package org.p2psimulator.entity.protocol.structured.storm;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.p2psimulator.entity.Id;
import org.p2psimulator.entity.Message;
import org.p2psimulator.entity.Peer;
import org.p2psimulator.entity.overlaynetwork.routingtable.StormRoutingTable;
import org.p2psimulator.entity.overlaynetwork.routingtable.WitnessesTable;
import org.p2psimulator.entity.protocol.structured.ChordProtocol;
import org.p2psimulator.entity.protocol.structured.chordcycles.ChordDownloadCycle;
import org.p2psimulator.entity.protocol.structured.chordcycles.ChordQueryCycle;
import org.p2psimulator.entity.protocol.structured.storm.stormcycles.StormGatherCycle;
import org.p2psimulator.entity.protocol.structured.storm.stormcycles.StormWitnessCycle;
import org.p2psimulator.entity.protocol.messages.chord.storm.JoinWitnessesRing;
import org.p2psimulator.entity.reputation.ReputationManager;
import org.p2psimulator.entity.reputation.ReputationRepository;
import org.p2psimulator.entity.reputation.ReputationRepositoryTypes;
import org.p2psimulator.engine.Environment;
import org.p2psimulator.entity.overlaynetwork.structures.StormWitnessRing;
import org.p2psimulator.entity.protocol.messages.chord.storm.CreateWitnessesRing;
import org.p2psimulator.entity.protocol.messages.chord.storm.FindWitness;
import org.p2psimulator.entity.protocol.messages.chord.storm.UpdateWitnesses;
import org.p2psimulator.entity.protocol.messages.chord.storm.WitnessFound;
import org.p2psimulator.entity.reputation.Reputation;

public class StormProtocol extends ChordProtocol {

    ChordQueryCycle queryCycle;
    ChordDownloadCycle downloadCycle;
    StormGatherCycle gatherCycle;
    StormWitnessCycle witnessCycle;
    Map<Id, Map<Id, WitnessesTable>> witnessesTablesMap;
    Map<Id, StormRoutingTable> routingTablesMap;

    public StormProtocol(Environment enviroment) {
        super(enviroment);
        this.totalCycles = 16;
        this.runningCycle = 0;


        routingTablesMap = new HashMap<Id, StormRoutingTable>();
        witnessesTablesMap = new HashMap<Id, Map<Id, WitnessesTable>>();

        gatherCycle = new StormGatherCycle(env, currentTransactionId, witnessesTablesMap, routingTablesMap);
        witnessCycle = new StormWitnessCycle(env, currentTransactionId, witnessesTablesMap, routingTablesMap);
        queryCycle = new ChordQueryCycle(env, currentTransactionId);
        downloadCycle = new ChordDownloadCycle(env, currentTransactionId);
    }

    @Override
    public String runCycle(int cycleId, Peer peer) {
        if (table == null) {
            table = getFingerTable(peer);
        }
        runningCycle = cycleId;
        String str = super.runCycle(cycleId, peer);
        if (!str.equals("")) {
            return str;
        } else {
            runningCycle = cycleId;
            switch (cycleId) {
                case 7:
                    queryCycle.setCurrentTransactionId(currentTransactionId);
                    return queryRequestCycle(peer);
                case 8:
                    queryCycle.setCurrentTransactionId(currentTransactionId);
                    return queryResponseCycle(peer);
                case 9:
                    gatherCycle.setCurrentTransactionId(currentTransactionId);
                    gatherCycle.setFingerTable(table);
                    return gatherCycle.runRequestCycle(peer);
                case 10:
                    gatherCycle.setCurrentTransactionId(currentTransactionId);
                    gatherCycle.setFingerTable(table);
                    return gatherCycle.runResponseCycle(peer);
                case 11:
                    downloadCycle.setCurrentTransactionId(currentTransactionId);
                    downloadCycle.setQueryKey(queryCycle.getSearchedKey());
                    downloadCycle.setServentId(gatherCycle.getBestServent());
                    return downloadCycle.runRequestCycle(peer);
                case 12:
                    downloadCycle.setCurrentTransactionId(currentTransactionId);
                    downloadCycle.setQueryKey(queryCycle.getSearchedKey());
                    downloadCycle.setServentId(gatherCycle.getBestServent());
                    return downloadCycle.runResponseCycle(peer);
                case 13:
                    witnessCycle.setCurrentTransactionId(currentTransactionId);
                    witnessCycle.setAvaliation(downloadCycle.getAvaliation());
                    witnessCycle.setServentId(downloadCycle.getServentId());
                    witnessCycle.setChordFingerTable(table);
                    return witnessCycle.runRequestCycle(peer);
                case 14:
                    witnessCycle.setCurrentTransactionId(currentTransactionId);
                    witnessCycle.setAvaliation(downloadCycle.getAvaliation());
                    witnessCycle.setServentId(downloadCycle.getServentId());
                    witnessCycle.setChordFingerTable(table);
                    return witnessCycle.runResponseCycle(peer);

                case 15:
                    witnessCycle.setCurrentTransactionId(currentTransactionId);
                    witnessCycle.setAvaliation(downloadCycle.getAvaliation());
                    witnessCycle.setServentId(downloadCycle.getServentId());
                    witnessCycle.setChordFingerTable(table);
                    return witnessCycle.runSecondRequestCycle(peer);
                case 16:
                    witnessCycle.setCurrentTransactionId(currentTransactionId);
                    witnessCycle.setAvaliation(downloadCycle.getAvaliation());
                    witnessCycle.setServentId(downloadCycle.getServentId());
                    return witnessCycle.runSecondResponseCycle(peer);
                default:
                    runningCycle = 0;
                    return "";
            }
        }
    }

    @Override
    public synchronized void answareSpecificProtocolMessages(Peer p,
            Message msg) {

        ReputationManager manager = env.getReputationManager();
        ReputationRepository rep = manager.getReputationRepository(ReputationRepositoryTypes.REP_SERVENT_REPOSITORY);

        p.getBehavior().updateReputation(p.getId(), rep);
        if (!p.getProcessedMessages().contains(msg.getMessageId())
                & p.getBehavior().answareMessage(p.getId(), msg)
                & p.isOnline()) {

            if (msg instanceof JoinWitnessesRing) {
                p.getProcessedMessages().add(msg.getMessageId());
                answareJoinWitnessRing(p, (JoinWitnessesRing) msg);
            } else if (msg instanceof FindWitness) {
                p.getProcessedMessages().add(msg.getMessageId());
                answareFindWitness(p, (FindWitness) msg);
                resendFindWitness(p, (FindWitness) msg);
                
            } else if (msg instanceof UpdateWitnesses) {
                p.getProcessedMessages().add(msg.getMessageId());
                answareUpdateWitness(p, (UpdateWitnesses) msg);
            } else if (msg instanceof CreateWitnessesRing) {
                p.getProcessedMessages().add(msg.getMessageId());
                answareCreateWitnessesRing(p, (CreateWitnessesRing) msg);
            } else {
                env.getNetwork().collectDiscardedMessage(msg);
            }

        }
    }

    private void answareCreateWitnessesRing(Peer p, CreateWitnessesRing msg) {

        StormRoutingTable routingTable = routingTablesMap.get(p.getId());
        if (routingTable == null) {
            routingTable = new StormRoutingTable(
                    env.getGeneralConfig().getFileConfig().getIdSpace(), p.getId());
            routingTablesMap.put(p.getId(), routingTable);
        }

        if (isPeerPredecessor(p, msg.getRefferedId())) {
            if (!routingTable.contains(msg.getRefferedId())) {
                Double ringSize = env.getGeneralConfig().getProtocolConfig().getStormWitnessSize();
                StormWitnessRing ring = new StormWitnessRing(ringSize.intValue());

                ring.insertNode(msg.getServentId(), msg.getOpinion());
                routingTable.insertNode(msg.getRefferedId(), ring);
            } else {

                // somente adiciona a nova testemunha
                StormWitnessRing ring = routingTable.getWitnessRing(msg.getRefferedId());
                if (ring == null) {
                    routingTable.deletePeer(msg.getRefferedId());
                    return;
                }
                ring.insertNode(msg.getServentId(), msg.getOpinion());
            }
        } else {
            resendCreateRing(p, msg);
        }

    }

    private void answareUpdateWitness(Peer p, UpdateWitnesses msg) {
        StormRoutingTable routingTable = routingTablesMap.get(p.getId());
        if (routingTable == null) {
            routingTable = new StormRoutingTable(
                    env.getGeneralConfig().getFileConfig().getIdSpace(), p.getId());
            routingTablesMap.put(p.getId(), routingTable);
        }

        if (routingTable.contains(msg.getRefferedId())) {

            StormWitnessRing ring = routingTable.getWitnessRing(msg.getRefferedId());
            if (ring == null) {
                routingTable.deletePeer(msg.getRefferedId());
                return;
            }
            ring.insertNode(msg.getServentId(), msg.getOpinion());
        }
    }

    private void answareFindWitness(Peer p, FindWitness msg) {

        StormRoutingTable routingTable = routingTablesMap.get(p.getId());
        if (routingTable == null) {
            routingTable = new StormRoutingTable(
                    env.getGeneralConfig().getFileConfig().getIdSpace(), p.getId());
            routingTablesMap.put(p.getId(), routingTable);
        }

        if (routingTable.contains(msg.getRefferedId())) {

            Id pred = p.getId();
            StormWitnessRing ring = routingTable.getWitnessRing(msg.getRefferedId());
            if (ring == null) {
                routingTable.deletePeer(msg.getRefferedId());
                return;
            }

            if (ring.size() > 0) {
                Id target = ring.getRandomWitness();
                Double d = (Double) target.getValue();
                if (!d.isNaN()) {

                    try {
                        Reputation opinion = ring.getValue(target);
                        WitnessFound witnessFound = new WitnessFound(p.getId(),
                                msg.getServentId(), msg.getTransactionId(), p.getBehavior().getBehaviorType());

                        witnessFound.setOpinion(opinion);
                        witnessFound.setReferredId(msg.getRefferedId());
                        witnessFound.setPred(pred);
                        witnessFound.setTarget(target);
                        witnessFound.setTTL(15);
                        env.getNetwork().sendMessage(witnessFound);
                    } catch (Exception e) {
                    }
                }
            }
            //List<Id> preds = routingTable.getAllPrecedingNodes(msg.getRefferedId());
            //if (preds.size() == 0) {

        }

    }

    private void resendFindWitness(Peer p, FindWitness msg) {
        List<Id> preds = table.getAllPrecedingNodes(msg.getRefferedId());
        //}
        for (Id preceed : preds) {
            FindWitness findWitness = new FindWitness(p.getId(), preceed,
                    msg.getTransactionId(), msg.getOriginBehavior());
            findWitness.setReferredId(msg.getRefferedId());
            findWitness.setServentId(msg.getServentId());
            findWitness.setTTL(msg.getTTL());
            env.getNetwork().sendMessage(findWitness);
        }
    }

    private void resendCreateRing(Peer p, CreateWitnessesRing msg) {
        List<Id> preds = table.getAllPrecedingNodes(msg.getRefferedId());
        //}
        for (Id preceed : preds) {
            CreateWitnessesRing createRing = new CreateWitnessesRing(p.getId(), preceed,
                    msg.getTransactionId(), msg.getOriginBehavior());
            createRing.setRefferedId(msg.getRefferedId());
            createRing.setServentId(msg.getServentId());
            createRing.setOpinion(msg.getOpinion());
            createRing.setTTL(msg.getTTL());
            env.getNetwork().sendMessage(createRing);
        }
    }

    private void answareJoinWitnessRing(Peer p, JoinWitnessesRing msg) {
        Id refferedServentId = msg.getReferredId();
        Id requesterId = msg.getServentId();


        Map<Id, WitnessesTable> map = witnessesTablesMap.get(p.getId());
        synchronized (witnessesTablesMap) {
            if (map == null) {
                map = new HashMap<Id, WitnessesTable>();
                witnessesTablesMap.put(p.getId(), map);
            }
        }
        WitnessesTable witnessesTable = map.get(refferedServentId);

        if (witnessesTable == null) {
            witnessesTable = new WitnessesTable(env.getGeneralConfig().getFileConfig().getIdSpace(),
                    refferedServentId, p.getId());
            map.put(refferedServentId, witnessesTable);
        }

        if (witnessesTable.isMySucessor(requesterId)) {
            witnessesTable.insertNode(requesterId, msg.getOpinion());

            List<Id> preds = witnessesTable.getAllPrecedingNodes(requesterId);
            for (Id id : preds) {
                forwardJoinRingMessage(p, id, msg);
            }
        } else {
            Id id = witnessesTable.getClosestPreceding(requesterId);
            Double d = (Double) id.getValue();
            if (d.isNaN()) {
                return;
            }
            forwardJoinRingMessage(p, id, msg);
        }
    }

    private String queryResponseCycle(Peer peer) {
        return queryCycle.runResponseCycle(peer);
    }

    private String queryRequestCycle(Peer peer) {
        return this.queryCycle.runRequestCycle(peer);
    }

    private void forwardJoinRingMessage(Peer p, Id dest, JoinWitnessesRing msg) {

        JoinWitnessesRing joinRing = new JoinWitnessesRing(p.getId(),
                dest, msg.getTransactionId(), msg.getOriginBehavior());

        joinRing.setServentId(msg.getServentId());
        joinRing.setReferredId(msg.getReferredId());
        joinRing.setOpinion(msg.getOpinion());
        joinRing.setTTL(msg.getTTL());
        env.getNetwork().sendMessage(joinRing);
    }

    @Override
    public String leaveNetworkCycle(Peer p) {

        String ret = super.leaveNetworkCycle(p);

        if (!ret.equals("")) {
            p.setOnline(false);
        }
        return ret;
    }

    @Override
    public String joinNetworkRequestCycle(Peer p) {

        String ret = super.joinNetworkRequestCycle(p);

        if (!ret.equals("")) {
            p.setOnline(true);
        }
        return ret;
    }

    @Override
    public String joinNetworkResponseCycle(Peer p) {

        return super.joinNetworkResponseCycle(p);
    }
}
